package com.jkgh.remedium.rsgen.generator.java;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.jkgh.remedium.rsgen.vo.Argument;
import com.jkgh.remedium.rsgen.vo.MethodInfo;
import com.jkgh.remedium.rsgen.vo.MethodObject;

public class RemediumMethodObjectJavaGenerator {

	private static final Map<String, String> CLASS_BY_TYPE = new HashMap<String, String>();

	private static final Map<String, String> RESPONDER_BY_TYPE = new HashMap<String, String>();

	static {
		CLASS_BY_TYPE.put("int", "Integer");
		CLASS_BY_TYPE.put("double", "Double");
		CLASS_BY_TYPE.put("byte", "Byte");

		RESPONDER_BY_TYPE.put("int", "Int");
		RESPONDER_BY_TYPE.put("int[]", "IntArray");
		RESPONDER_BY_TYPE.put("byte", "Byte");
		RESPONDER_BY_TYPE.put("byte[]", "ByteArray");
		RESPONDER_BY_TYPE.put("double", "Double");
		RESPONDER_BY_TYPE.put("double[]", "DoubleArray");
		RESPONDER_BY_TYPE.put("String", "String");
		RESPONDER_BY_TYPE.put("String[]", "StringArray");
	}

	public static void generate(File outputDir, MethodObject m, String packageName) throws FileNotFoundException {

		File outFile = new File(outputDir, m.getClassName() + ".java");
		PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream(outFile)));

		out.println("package " + packageName + ";");
		out.println();
		out.println("import com.jkgh.asin.*;");
		out.println("import com.jkgh.asin.buffers.*;");
		out.println("import com.jkgh.remedium.*;");
		out.println("import com.jkgh.remedium.objects.*;");
		out.println("import com.jkgh.remedium.objects.messages.*;");
		out.println("import com.jkgh.remedium.objects.messages.out.*;");
		out.println("import com.jkgh.remedium.states.*;");
		out.println("import com.jkgh.remedium.tools.*;");
		out.println();
		out.println("/**");
		out.println(" * " + m.getDescription());
		out.println(" */");
		out.println("public abstract class " + m.getClassName() + " extends RemediumMethodObject {");
		out.println();

		for (MethodInfo mt : m.getMethods()) {
			out.println("	/**");
			out.println("	 * " + mt.getDescription());
			out.println("	 * ");

			for (Argument a : mt.getArguments()) {
				out.println("	 * @param " + a.getName() + " " + a.getDescription());
			}

			if (mt.getReturnType() != null) {
				out.println("	 * ");
				out.println("	 * @param returner Should return: " + mt.getReturnType().getDescription() + ". ");
			}

			String as = "";
			for (Argument a : mt.getArguments()) {
				as += a.getClassName() + " " + a.getName() + ", ";
			}

			if (mt.getReturnType() != null) {
				as += "Returner<" + mt.getReturnType().getClassName() + "> returner";
			} else if (as.length() > 0) {
				as = as.substring(0, as.length() - 2);
			}

			out.println("	 */");
			out.println("	public abstract void " + mt.getName() + "(" + as + ");");
			out.println();
		}

		out.println("	// --------------------------------------------------------------------------------");
		out.println();
		out.println("	public " + m.getClassName() + "(RemediumServer server) {");
		out.println("		super(server);");
		out.println("	}");
		out.println();
		out.println("	protected " + m.getClassName() + "(int hostID, int objectID, RemediumServer server) {");
		out.println("		super(hostID, objectID, server);");
		out.println("	}");
		out.println();
		out.println("	@Override");
		out.println("	public void call(final StateMachine machine, final RemediumInMessage message, final int callID, int methodID, final Callback callback) {");
		out.println("		switch (methodID) {");

		for (int i = 0; i < m.getMethods().size(); ++i) {
			MethodInfo mt = m.getMethods().get(i);

			out.println();
			out.println("			case " + i + ":");

			String args = "";
			for (Argument a : mt.getArguments()) {
				args += a.getName() + i + ", ";
			}

			List<String> ending = new ArrayList<String>();
			if (mt.getReturnType() == null) {
				if (args.length() > 0) {
					args = args.substring(0, args.length() - 2);
				}
				ending.add(mt.getName() + "(" + args + ");");
				ending.add("callback.go();");
			} else {
				ending.add(mt.getName() + "(" + args + "new Returner<" + classForType(mt.getReturnType().getClassName()) + ">() {");
				ending.add("");
				ending.add("	@Override");
				ending.add("	public void returned(" + classForType(mt.getReturnType().getClassName()) + " r) {");
				ending.add("		RemediumS2C" + responderForType(mt.getReturnType().getClassName()) + "Responder responder = new RemediumS2C" + responderForType(mt.getReturnType().getClassName())
					+ "Responder(callID, r);");
				ending.add("		responder.produce(machine);");
				ending.add("	}");
				ending.add("});");
				ending.add("callback.go();");
			}

			RemediumArgumentConsumerJavaGenerator.generate(out, 4, i, mt.getArguments(), ending);

			out.println("				break;");

		}

		out.println("		}");
		out.println("	}");
		out.println();
		out.println("	public static void consume(final StateMachine machine, final RemediumInMessage message, final Future<" + m.getClassName() + "> future) {");
		out.println("		machine.setCurrentState(new GatheringState(machine, 1) {");
		out.println();
		out.println("			@Override");
		out.println("			protected void onNeededBytesAvailable() {");
		out.println("				switch (machine.produceByte()) {");
		out.println();
		out.println("					case RemediumConstants.NULL:");
		out.println("						future.goWith(null);");
		out.println("						break;");
		out.println();
		out.println("					case RemediumConstants.HISTORY:");
		out.println("						future.goWith((" + m.getClassName() + ") message.fromHistory(machine.produceInt()));");
		out.println("						break;");
		out.println();
		out.println("					case RemediumConstants.VALUE:");
		out.println("						consumeContent(machine, message, future);");
		out.println("						break;");
		out.println("				}");
		out.println("			}");
		out.println("		});");
		out.println("	}");
		out.println();
		out.println("	public static void consumeArray(final StateMachine machine, final RemediumInMessage message, final Future<" + m.getClassName() + "[]> future) {");
		out.println("		machine.setCurrentState(new GatheringState(machine, 4) {");
		out.println();
		out.println("			@Override");
		out.println("			protected void onNeededBytesAvailable() {");
		out.println("				final int length = machine.produceInt();");
		out.println("				if (length == -1) {");
		out.println("					future.goWith(null);");
		out.println("				} else if (length == 0) {");
		out.println("					future.goWith(new " + m.getClassName() + "[0]);");
		out.println("				} else {");
		out.println("					consume(machine, message, new Future<" + m.getClassName() + ">() {");
		out.println();
		out.println("						private int i = 0;");
		out.println("						private " + m.getClassName() + "[] result = new " + m.getClassName() + "[length];");
		out.println();
		out.println("						@Override");
		out.println("						public void goWith(" + m.getClassName() + " o) {");
		out.println("							result[i++] = o;");
		out.println("							if (i == length) {");
		out.println("								future.goWith(result);");
		out.println("							} else {");
		out.println("								consume(machine, message, this);");
		out.println("							}");
		out.println("						}");
		out.println("					});");
		out.println("				}");
		out.println("			}");
		out.println("		});");
		out.println("	}");
		out.println();
		out.println("	private static void consumeContent(final StateMachine machine, final RemediumInMessage message, final Future<" + m.getClassName() + "> future) {");
		out.println("		machine.setCurrentState(new GatheringState(machine, 8) {");
		out.println();
		out.println("			@Override");
		out.println("			protected void onNeededBytesAvailable() {");
		out.println();
		out.println("				int hostID = machine.produceInt();");
		out.println("				int objectID = machine.produceInt();");
		out.println();
		out.println("				RemediumHost host = message.getServer().getHost(hostID);");
		out.println("				" + m.getClassName() + " methodObject = (" + m.getClassName() + ") host.getMethodObject(objectID);");
		out.println("				if (methodObject == null) {");
		out.println("					methodObject = new " + m.getClassName() + "Redirector(hostID, objectID, message.getServer());");
		out.println("					host.registerMethodObject(objectID, methodObject);");
		out.println("				}");
		out.println();
		out.println("				message.toHistory(methodObject);");
		out.println();
		out.println("				future.goWith(methodObject);");
		out.println("			}");
		out.println("		});");
		out.println("	}");
		out.println();
		out.println("	private static class " + m.getClassName() + "Redirector extends " + m.getClassName() + " {");
		out.println();
		out.println("		private final RemediumHost host;");
		out.println();
		out.println("		protected " + m.getClassName() + "Redirector(int hostID, int objectID, RemediumServer server) {");
		out.println("			super(hostID, objectID, server);");
		out.println();
		out.println("			this.host = server.getHost(hostID);");
		out.println("		}");

		for (int i = 0; i < m.getMethods().size(); ++i) {

			MethodInfo mt = m.getMethods().get(i);

			String finalArgs = "";
			for (Argument a : mt.getArguments()) {
				finalArgs += "final " + a.getClassName() + " " + a.getName() + ", ";
			}

			if (mt.getReturnType() != null) {
				finalArgs += "Returner<" + classForType(mt.getReturnType().getClassName()) + "> returner";
			} else if (finalArgs.length() > 0) {
				finalArgs = finalArgs.substring(0, finalArgs.length() - 2);
			}

			out.println();
			out.println("		@Override");
			out.println("		public void " + mt.getName() + "(" + finalArgs + ") {");
			out.println("			RemediumS2CCaller caller = new RemediumS2CCaller(objectID, " + i + ", " + (mt.getReturnType() == null ? "0" : "host.registerCall(returner)") + ") {");
			out.println();
			out.println("				@Override");
			out.println("				protected void produceArguments(DataConsumer consumer) {");

			for (Argument a : mt.getArguments()) {
				out.println("					" + RemediumProducerJavaGenerator.generate(a.getClassName(), a.getName(), "this") + ";");
			}

			out.println("				}");
			out.println("			};");
			out.println("			caller.produce(host.getMachine());");
			out.println("		}");
		}

		out.println("	}");
		out.println("}");

		out.close();
	}

	private static String responderForType(String type) {

		String ret = RESPONDER_BY_TYPE.get(type);
		if (ret == null) {
			if (type.endsWith("[]")) {
				ret = "ObjectArray";
			} else {
				ret = "Object";
			}
		}
		return ret;
	}

	private static String classForType(String type) {

		String cls = CLASS_BY_TYPE.get(type);
		if (cls == null) {
			cls = type;
		}
		return cls;
	}
}
